
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Seam {
	public final static boolean SHOW_LOG = true;
	public final static boolean WRITE_GRADIENT_IMAGE = true;

	/**
	 * (inputPath, output #col, output #row, energy type```, outputPath)
	 * Example:
	 * C:\temp\Images\fujifrom.jpg 500 453 0 C:\temp\Images\FujiSeames.jpg
	 * Note: 595 453, only scales in col
	 */
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		
		
		//######################Prepare Data###########################
		
		//Read Input
		String inputPath = args[0];
		String outputPath = args[4];
		int type = Integer.parseInt(args[3]);
		int outputCol = Integer.parseInt(args[1]);
		int outputRow = Integer.parseInt(args[2]);
		BufferedImage orgImg = null;
		
		//Read Image and Initialize Variables
		try {
			orgImg = ImageIO.read( new File( inputPath ) );
		} catch (IOException e) {
			// TODO Should handle exceptions?
			e.printStackTrace();
		}
		int orgCol = orgImg.getWidth();
		int orgRow = orgImg.getHeight();
		int numOfHorizontalSeams = orgCol - outputCol;
		int numOfVerticalSeams = orgRow - outputRow;
		BufferedImage gradientMap = 
				new BufferedImage(orgCol, orgRow, BufferedImage.TYPE_BYTE_GRAY);
		double[][] gradientMatrix;

		
		
		//######################Create EnergyMap###########################
		
		if (type == 0){
			
		//Create EnergyImage Type0
		gradientMatrix = createGradiantMapType0( orgCol,  orgRow,  orgImg);
		
		} 
		
		else if (type == 1) {
			
		//Create EnergyImage Type1
		gradientMatrix = createGradiantMapType1( orgCol,  orgRow,  orgImg);
		
		} 
		
		else if (type == 2) {
			
		//Create EnergyImage Type2
		gradientMatrix = createGradiantMapType2(orgCol, orgRow , orgImg);
		
		} 
		else {
			System.out.print("Invalid Type.\n");
			return;
		}
		

		
		
		gradientMap = initGradientMap(orgCol, orgRow, gradientMatrix);
		
		
		//######################Additional Implementations###########################
		
		
		///##### Generate Seams Matrix########
		
		
		// Retrieve Seams
		
		//Remove Seams \ Duplicate Seams
		
		
		
		
		//######################DEBUG###########################
		if (SHOW_LOG){
			System.out.print("Input Path = " +  inputPath + '\n');
			System.out.print("Original Dimensions = " +  
					orgCol + ' ' + orgRow + '\n');
			System.out.print("Output Path = " +  outputPath + '\n');
			System.out.print("New Dimensions = " +  
					outputCol + ' ' + outputRow + '\n');
			System.out.print("Number of Seams = " +  
					numOfHorizontalSeams + ' ' + numOfVerticalSeams + '\n');
			System.out.print("Energy Type = " +  type + '\n');
			File outputfile = new File("image.png");
			ImageIO.write(gradientMap, "png", outputfile);
		}
		System.out.print("Image Generated");

	}
	
	private static double[][] createGradiantMapType2(int orgCol, int orgRow, BufferedImage orgImg) {
		// TODO Auto-generated method stub
		double[][] forwardMatrix = new double[orgCol][orgRow];
		int i, j;
		double CL, CU, CR, val1, val2, val3;
		
		for ( i = 1; i < orgCol - 1; i++){// TODO: WHAT to do on edges?
			for ( j = 1; j < orgRow - 1; j++){
				
				
				CL = Math.abs(RGB2Gray( orgImg, i, j+1) - RGB2Gray( orgImg, i, j-1)) + 
						Math.abs(RGB2Gray( orgImg, i-1, j) - RGB2Gray( orgImg, i, j-1));
				CU = Math.abs(RGB2Gray( orgImg, i, j+1)  -  RGB2Gray( orgImg, i, j-1) );
				CR = Math.abs(RGB2Gray( orgImg, i, j+1)  - RGB2Gray( orgImg, i, j-1)) + 
						Math.abs(RGB2Gray( orgImg, i-1, j) -  RGB2Gray( orgImg, i, j+1));
				
				val1 = forwardMatrix[i-1][j-1] + CL;
				val2 = forwardMatrix[i-1][j] + CU;
				val3 = forwardMatrix[i-1][j+1] + CR;
				
				/*
				CL = Math.abs(orgImg.getRGB(i, j+1) - orgImg.getRGB(i, j-1)) + 
						Math.abs(orgImg.getRGB(i-1, j) - orgImg.getRGB(i, j - 1));
				CU = Math.abs(orgImg.getRGB(i, j+1)  -  orgImg.getRGB(i, j-1) );
				CR = Math.abs(orgImg.getRGB(i, j+1)  - orgImg.getRGB(i, j-1)) + 
						Math.abs(orgImg.getRGB(i-1, j) -  orgImg.getRGB(i, j + 1));
				
				val1 = forwardMatrix[i-1][j-1] + CL;
				val2 = forwardMatrix[i-1][j] + CU;
				val3 = forwardMatrix[i-1][j+1] + CR;
				*/
				
				//TODO: what's P???
				forwardMatrix[i][j] = Math.min(Math.min(val1, val2),val3); 
				if (SHOW_LOG){
					System.out.print("Pix = " +  i + ' ' +  j + " Value = " + forwardMatrix[i][j] + '\n');
				}
			}
		}
		
		return forwardMatrix;
	}

	private static BufferedImage initGradientMap(int orgCol, int orgRow,
			double[][] gradientMatrix) {
		BufferedImage gradientMap = 
				new BufferedImage(orgCol, orgRow, BufferedImage.TYPE_BYTE_GRAY);
		int grayLevel, i, j;
		if (WRITE_GRADIENT_IMAGE){
			for ( i = 0; i < orgCol; i++){
				for ( j = 0; j < orgRow; j++){
					grayLevel = (int)gradientMatrix[i][j];
					gradientMap.setRGB(i, j, (grayLevel << 16) + (grayLevel << 8) + grayLevel);
;
				}
			}
		}
		
		return gradientMap;
	}

	public static double colorDist(int clr1, int clr2) {
		//Returns NORM between two colors
		//Returns int
		

        int red1 = (clr1 & 0x00ff0000) >> 16;
        int green1 = (clr1 & 0x0000ff00) >> 8;
        int blue1 = clr1 & 0x000000ff;
        
        int red2 = (clr2 & 0x00ff0000) >> 16;
        int green2 = (clr2 & 0x0000ff00) >> 8;
        int blue2 = clr2 & 0x000000ff;

        double dist = (Math.abs(red1 - red2) + Math.abs(green1 - green2) + Math.abs(blue1 - blue2)) / 3;

		return dist;
	}

	public static double[][] createGradiantMapType0(int orgCol, int orgRow, BufferedImage orgImg){
	int i, j, k, m, clr, numOfNeighboors;
	double pixVal;
	double[][] gradientMatrix = new double[orgCol][orgRow];
	
	for ( i = 0; i < orgCol; i++){
		for ( j = 0; j < orgRow; j++){
			pixVal = 0;
			numOfNeighboors = 0;
			clr = orgImg.getRGB(i,j);
			for (k = -1; k < 2; k ++){
				for(m = -1; m < 2; m ++){
					if ((i + k) == -1 || (j + m) == -1 || (i + k) == orgCol || (j + m) == orgRow){
						continue;
					}
					pixVal += 
						colorDist(clr, orgImg.getRGB(i + k, j + m));
					numOfNeighboors += 1;
				}
			} 
			
			gradientMatrix[i][j] = pixVal / (numOfNeighboors - 1);
			numOfNeighboors -=1;
	
			
			
			//TODO: delete?
			if (SHOW_LOG){
				//System.out.print("Pix = " +  i + ' ' +  j + " Value = " + gradientMatrix[i][j] + '\n');
			}
		}
	}
	
	return gradientMatrix;
}

	public static double[][] createGradiantMapType1(int orgCol, int orgRow, BufferedImage orgImg){
		
		double[][] gradientMatrix = createGradiantMapType0( orgCol,  orgRow,  orgImg);
		double[][] entropyMatrix = createEntropyMap( orgCol,  orgRow,  orgImg);
		gradientMatrix = matrixAdd(entropyMatrix, gradientMatrix);
		
		return gradientMatrix;
	}

	public static double[][] createEntropyMap(int orgCol, int orgRow, BufferedImage Img){
		int i, j, k, m;
		double pixVal;
		double[][] entropyMap = new double[orgCol][orgRow];

		
		for ( i = 0; i < orgCol; i++){
			for ( j = 0; j < orgRow; j++){
				pixVal = 0;
				
				for (k = -2; k < 3; k ++){
					for(m = -2; m < 3; m ++){
						if ((i + k) <= -1 || (j + m) <= -1 || (i + k) >= orgCol || (j + m) >= orgRow){
							continue;
						}
						

						pixVal -= 
							calEntropy(i , j, Img, orgCol, orgRow);
					}
				} 
				
				entropyMap[i][j] = pixVal;
		
				
				
				//TODO: delete?
				if (SHOW_LOG){
					System.out.print("Pix = " +  i + ' ' +  j + " Value = " + entropyMap[i][j] + '\n');
				}
			}
		}
		
		return entropyMap;
	}

	private static double calEntropy(int i, int j, BufferedImage img, int orgCol, int orgRow) {
		// TODO Auto-generated method stub
		//TODO: Can save calculations?
		double P = 0, graySums = 0;
		
		for (int k = -2; k < 3; k ++){
			for(int m = -2; m < 3; m ++){
				if ((i + k) <= -1 || (j + m) <= -1 || (i + k) >= orgCol || (j + m) >= orgRow){
					continue;
				}
				graySums += (img.getRGB(i + k, j + m) & 0xFF); //TODO: how can save normal weight?
			}
		}
		P = RGB2Gray( img, i, j) / graySums; //TODO: should read grayScaleValue
		return P * (Math.log(P)/Math.log(2));

	}

	public static double[][] matrixAdd(double[][] A, double[][] B){
		double[][]C = new double[A.length][A[0].length];

	   for(int i =0; i < A.length; i++){
		   for(int j=0; j < A[i].length;j++){
			   C[i][j] = A[i][j] + B[i][j]; 
		   }
	   }
	   return C;
	}



	public static int RGB2Gray(BufferedImage img, int i, int j){
		int clr = img.getRGB(i, j);
	    int red = (clr & 0x00ff0000) >> 16;
	    int green = (clr & 0x0000ff00) >> 8;
	    int blue = clr & 0x000000ff;
		return (red + green + blue) / 3;
	}

}

